package com.nf.mvc.configuration;

import com.nf.mvc.DispatcherServlet;
import com.nf.mvc.MvcContext;
import com.nf.mvc.ioc.BeanFactory;
import com.nf.mvc.ioc.Injected;

import javax.servlet.ServletConfig;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;

/**
 * 在整个mvc框架已经完成之后,自己有点想额外添加嵌入式tomcat与yml功能到框架中以模仿spring boot的功能,
 * 所以就引入了{@link com.nf.mvc.MvcApplication },{@link EnableConfigurationProperties},
 * {@link ConfigurationProperties},{@link YmlParser}等类型
 * <h3>想达成的典型用法</h3>
 * <ul>
 *     <li>在入口类上添加注解{@link EnableConfigurationProperties}指定有哪些配置属性类</li>
 *     <li>编写配置属性类，并在此类上添加注解{@link ConfigurationProperties},并指定前缀</li>
 *     <li>框架会自动把配置文件中对应前缀下的配置项赋值给配置属性类的同名属性</li>
 *     <li>在要使用配置属性的类中，声明一个类型为配置属性类的字段，并在其上添加注解{@link Injected}</li>
 *     <li>框架解析这个字段，自动注入相关的配置属性类实例</li>
 * </ul>
 * 示例代码如下:
 * <pre class="code">
 *
 * &#064;EnableConfigurationProperties({MyConfigurationProperties})
 * public class MyApplication{
 *     public static void main(String[] args) {
 *
 *     }
 * }
 * &#064;ConfigurationProperties("server")
 * public class MyConfigurationProperties{
 *     private int port;
 *     private String path;
 *     //getter setter...
 * }
 *
 * public class SomeClass {
 *     &#064;Injected
 *     private MyConfigurationProperties config;
 *     public void doSth(){
 *         int port = config.getPort();
 *         String path = config.getPath();
 *     }
 * }
 * yml配置如下:
 * server:
 *      port: 8000
 *      path: /somePath
 *
 * </pre>
 * <h3>实际实现的结果</h3>
 * <li>{@link EnableConfigurationProperties}注解完全没用上</li>
 * <li>只要类上修饰了{@link ConfigurationProperties}注解就认为是一个配置属性类</li>
 * <li>在框架的初始化阶段会把配置属性类实例化并保存到{@link com.nf.mvc.MvcContext}中</li>
 * <li>被框架管理的实例依赖配置属性类，框架会自动把配置属性类实例注入到这些受管理的实例中去</li>
 * <li>用户可以通过{@link MvcContext#getConfigurationProperties()}访问所有的配置属性类，这样就可以达成手动注入的效果</li>
 *
 * <h3>实现的思考过程</h3>
 * 这里记录下我当初想做的功能与最终实现的结果为什么有差异，我为什么这么实现，以便让学生更好的理解我的思路
 * <h4>主要目的</h4>
 * <p>想把spring boot的读取yml配置文件的特性引入到本mvc框架中来,使用方式也与spring boot类似</p>
 * 并且打算让配置文件只有一个,位置与名字都是固定的,位置就是在类路径下,名字固定是application.yml。
 * EnableConfigurationProperties注解修饰在启动类上,用来指定哪些类是配置属性类,
 * 配置属性类是修饰了ConfigurationProperties注解的类型,此类型的属性来自于yml配置文件,
 * 要使用这些配置类的类型,在其中声明一个字段并在其上加入Injected注解,表明需要注入配置属性类.
 *
 * <h4>配置属性类的解析时机</h4>
 * <p>由于MvcApplication的run方法也可能有使用配置属性类,比如读取port设置,如果在这里解析这些配置属性类,这些解析后的配置类实例就没有一个存放的地方
 * 因为此时此刻DispatcherServlet还没有初始化，MvcContext对象还不存在，所以放在这里不合适</p>
 *
 * <p>除非把扫描类的逻辑进行改写,这可能也影响DispatcherServlet的编写,MvcApplication是最后才添加的,不想这么大动干戈</p>
 *
 * <p>所以,要么放弃在run方法里解析配置属性类,要么像spring cloud一样,引入一个bootstrap.yml文件专门用在这种启动mvc项目的方式上,
 * 然后application.yml文件放置在DispatcherServlet的init里进行解析</p>
 *
 * <p><i>最后的决定:由于教学的目的,最后MvcApplication类放弃对yml文件的处理,MvcApplication的配置信息通过命令行参数的方式进行处理，
 * 而配置类的解析处理放在{@link DispatcherServlet#init}初始化阶段处理</i></p>
 *
 * <h4>默认配置属性类与定制配置属性类</h4>
 * 决定了在{@link DispatcherServlet#init}方法进行配置属性的处理之后,我就在initMvcContext时解析并实例化了配置属性类,
 * 并放置到了MvcContext里.之后我在DispatcherServlet里编写代码的过程中,也想像其它Mvc组件一样,分成定制与框架提供这两种不同的配置属性类,
 * 但目前框架没有任何内部的配置属性类，所以简化起见，省略掉了内部配置属性类的处理
 *
 * <h4>哪些类型可以使用配置属性类?</h4>
 * 首先,能使用的类型的实例肯定是被mvc管理的或者是可以被mvc框架获取到的,其次,也牵涉到使用的类型是单例还是原型的.
 * 因为是原型实例的话,这些实例注入配置属性类就不能在{@link com.nf.mvc.DispatcherServlet#init(ServletConfig)}中实现,
 * 因为它只会给一个实例注入一次(init只执行一次)
 * <p>原型实例不能在{@link com.nf.mvc.DispatcherServlet#init(ServletConfig)}
 * 中注入配置属性类就只能在{@link BeanFactory#getBean(Class)}这里进行注入处理,因为整个mvc框架中,
 * 所有被框架管理的类型实例都是靠{@link BeanFactory#getBean(Class)}来实例化的.</p>
 *
 * <p>所以,我最终选择在{@link BeanFactory#getBean(Class)}这里进行配置属性类的注入处理,
 * 这样就不用理会其是否是单例还是原型的问题.</p>
 *
 * <p>当决定在{@link BeanFactory#getBean(Class)}里进行处理时,又想到有些类的实例化是
 * 在{@link DispatcherServlet}初始化的时候就实例化了,见{@link com.nf.mvc.MvcContext#resolveMvcClass(Class, Class, List)},
 * 这样的话,如果非要在{@link BeanFactory#getBean(Class)}这里进行注入处理,就必须让配置类先解析处理完毕</p>
 *
 * <p><i>最后的决定:在{@link com.nf.mvc.MvcContext#resolveMvcClass(Class, Class, List)}进行配置类解析处理,
 * 并且 <b>必须</b>先处理这个工作之后,再去干别的事情</i></p>
 * <h4>yml文件解析</h4>
 * <p>yml文件解析工作只要在使用解析结果之前处理掉就可以了，因为yml文件只会解析一次，解析的结果会保存在{@link YmlParser}类的一个map中，
 * 既然配置属性类是在初始化的时候解析的，那么yml的解析就只能也在初始化或{@link DispatcherServlet}的静态代码块或实例代码块进行了</p>
 * <h4>配置属性的嵌套注入问题</h4>
 * <p>配置属性的嵌套注入牵涉到配置属性类的依赖顺序处理，yml前缀处理等，比较复杂，所以直接放弃。
 * 因而就不能用{@link BeanFactory#getBean(Class)}来对配置属性类进行实例化处理，
 * 要对配置属性类的实例化编写单独的代码，相应的，其它任何类型的注入处理也没有嵌套逻辑的实现，这是mvc框架不是ioc框架</p>
 *
 * <p>整个思考过程,深感没有一个容器功能来管理所有类型的不便,这也没有办法,这个框架本来只是一个教学用的Mvc框架,不能搞太多功能</p>
 * <h3>参考资料</h3>
 * <a href="https://blog.csdn.net/m0_37556444/article/details/113513089">推断main函数所在的类（来自于spring boot)</a>
 * <a href="https://www.codenong.com/11306811/">获取方法调用者所在的类</a>
 *
 * @author cj
 * @see com.nf.mvc.MvcContext
 * @see ConfigurationProperties
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableConfigurationProperties {
    Class<?>[] value() default {};
}
